#include <vector>
#include <map>
#include <queue>
using namespace std;
#include <boost/shared_ptr.hpp>
#include <wx/wx.h>
#include <wx/dir.h>
#include <wx/listctrl.h>
#include <wx/file.h>
#include <wx/thread.h>
#include <wx/filename.h>
#include "RWLock.h"
#include "ItemInfo.h"
#include "Utils.h"
#include "FilePropertiesWatcher.h"
#include "FileOperation.h"
#include "DirectoryCache.h"
#include "SyncQueue.h"
#include "BackgroundIconLoader.h"
#include "Application.h"

FileOperationManager::FileOperationManager()
{
    totalWork.QuadPart = 0;
    currentWork.QuadPart = 0;
}
bool FileOperationManager::PerformOperation(const FileOperation &fop)
{
    FileOperationThread *fopThread = new FileOperationThread(fop);
    fopThread->Create();
    fopThread->Run();
    return true;
}
void FileOperationManager::AddTotalWork(LARGE_INTEGER work)
{
    totalWork.QuadPart += work.QuadPart;
    for (unsigned int i = 0; i < watchers.size(); i++)
        watchers[i]->TotalWork(totalWork);
}
void FileOperationManager::SubtractTotalWork(LARGE_INTEGER work)
{
    totalWork.QuadPart -= work.QuadPart;
    for (unsigned int i = 0; i < watchers.size(); i++)
        watchers[i]->TotalWork(totalWork);
}
void FileOperationManager::AddCurrentWork(LARGE_INTEGER work)
{
    currentWork.QuadPart += work.QuadPart;
    for (unsigned int i = 0; i < watchers.size(); i++)
        watchers[i]->CurrentWork(currentWork);
}
void FileOperationManager::SubtractCurrentWork(LARGE_INTEGER work)
{
    currentWork.QuadPart -= work.QuadPart;
    for (unsigned int i = 0; i < watchers.size(); i++)
        watchers[i]->CurrentWork(currentWork);
}
void FileOperationManager::RegisterWorkWatcher(WorkWatcher* watcher)
{
    watchers.push_back(watcher);
}
FileOperation::FileOperation()
{

}
void FileOperation::SetType(FileOperationType type)
{
    this->type = type;
}
FileOperationType FileOperation::GetType()
{
    return type;
}
void FileOperation::AddDirectory(const wxString &directory)
{
    directories.push_back(directory);
}
void FileOperation::AddFile(const wxString &file)
{
    files.push_back(file);
}
void FileOperation::SetBasePath(const wxString &basePath)
{
    this->basePath = basePath;
}
void FileOperation::SetDestinationPath(const wxString &destPath)
{
    this->destPath = destPath;
}
wxString FileOperation::GetDestinationPath()
{
    return destPath;
}
void FileOperation::SetWorkWatcher(WorkWatcherPtr workWatcher)
{
    this->workWatcher = workWatcher;
}
void FileOperation::RegisterFilePropertiesWatcher(FilePropertiesWatcher *filePropertiesWatcher)
{
    this->filePropertiesWatchers.push_back(filePropertiesWatcher);
}
wxArrayString &FileOperation::GetFileList()
{
    return files;
}
wxArrayString &FileOperation::GetDirectoryList()
{
    return directories;
}
wxString SubstitutePath(const wxString &basePath, const wxString &srcPath, const wxString &destPath)
{
    wxString finalBasePath;
    if (basePath.empty())
    {
        wxFileName file(srcPath);
        wxString topPath, topPathName;
        wxFileName::SplitPath(srcPath, &topPath, &topPathName, NULL, wxPATH_NATIVE);
        finalBasePath = topPath;
    }
    else
        finalBasePath = basePath;
    wxString ret = destPath;
    unsigned int i = 0;
    while (i < finalBasePath.size() && i < srcPath.size() && finalBasePath[i] == srcPath[i])
        i++;
    if (ret.Last() == '\\')
        ret.RemoveLast();
    while (i < srcPath.size())
        ret.Append(srcPath[i++]);

    return ret;
}
DWORD CALLBACK CopyProgressRoutine(LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred, LARGE_INTEGER StreamSize,
                                   LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber, DWORD dwCallbackReason, HANDLE hSourceFile,
                                   HANDLE hDestinationFile, LPVOID lpData)
{
    FileOperation *fop = (FileOperation *) lpData;
    LARGE_INTEGER diff;
    diff.QuadPart = TotalBytesTransferred.QuadPart - fop->lastProgress.QuadPart;
    fop->currentProgress.QuadPart += diff.QuadPart;

    ((App*) wxTheApp)->GetFileOperationManager()->AddCurrentWork(diff);
    fop->lastProgress = TotalBytesTransferred;
    FileOperationProgress p = FOP_PROGRESS_CONTINUE;
    if (fop->workWatcher)
        p = fop->workWatcher->CurrentWork(fop->currentProgress);
    if (p == FOP_PROGRESS_CANCEL)
        return PROGRESS_CANCEL;

    return PROGRESS_CONTINUE;
}
FileOperationProgress FileOperationThread::PerfromOperation(FileTreeNodePtr root)
{
    if (fop.type == FOP_COPY)
    {
        if (root->fullPath != fop.basePath)
        {
            wxString destDir = SubstitutePath(fop.basePath, root->fullPath, fop.destPath);
            if (!wxDirExists(destDir))
                wxMkdir(destDir);
        }

        for (unsigned int i = 0; i < root->files.size(); i++)
        {
            wxString destFile = SubstitutePath(fop.basePath, root->files[i], fop.destPath);
            fop.lastProgress.QuadPart = 0;
            FileOperationProgress p = FOP_PROGRESS_CONTINUE;
            if (fop.workWatcher)
                p = fop.workWatcher->CurrentFile(root->files[i]);
            if (p == FOP_PROGRESS_CANCEL)
                return p;

            if (CopyFileEx(root->files[i], destFile, CopyProgressRoutine, &fop, NULL, 0) == 0)
            {
                if (GetLastError() == ERROR_REQUEST_ABORTED)
                    return FOP_PROGRESS_CANCEL;
            }
            else
            {
                if (!fop.filePropertiesWatchers.empty())
                {
                    FileProperties fp;
                    fp.changed = FILE_PROPERTY_SIZE;
                    fp.size = root->fileSizes[i];
                    for (unsigned int  i = 0; i < fop.filePropertiesWatchers.size(); i++)
                        fop.filePropertiesWatchers[i]->FilePropertyChanged(destFile, fp);
                }
            }
        }
        for (unsigned int i = 0; i < root->directories.size(); i++)
        {
            FileOperationProgress p = PerfromOperation(root->directories[i]);
            if (p == FOP_PROGRESS_CANCEL)
                return p;
        }
    }
    else if (fop.type == FOP_MOVE)
    {
        for (unsigned int i = 0; i < root->files.size(); i++)
        {
            wxString destFile = SubstitutePath(fop.basePath, root->files[i], fop.destPath);
            fop.lastProgress.QuadPart = 0;
            FileOperationProgress p = FOP_PROGRESS_CONTINUE;
            if (fop.workWatcher)
                p = fop.workWatcher->CurrentFile(root->files[i]);
            if (p == FOP_PROGRESS_CANCEL)
                return p;

            if (MoveFileWithProgress(root->files[i], destFile, CopyProgressRoutine, &fop, 0) == 0)
            {
                DWORD result = GetLastError();
                if (result == ERROR_NOT_SAME_DEVICE)
                {
                    if (MoveFileWithProgress(root->files[i], destFile, CopyProgressRoutine, &fop, MOVEFILE_COPY_ALLOWED) == 0  && GetLastError() == ERROR_REQUEST_ABORTED)
                        return FOP_PROGRESS_CANCEL;
                }
            }
            else
            {
                fop.currentProgress.QuadPart += root->fileSizes[i].QuadPart;
                ((App*) wxTheApp)->GetFileOperationManager()->AddCurrentWork(root->fileSizes[i]);
                FileOperationProgress p = FOP_PROGRESS_CONTINUE;
                if (fop.workWatcher)
                    p = fop.workWatcher->CurrentWork(fop.currentProgress);
                if (p == FOP_PROGRESS_CANCEL)
                    return p;
            }

        }
        for (unsigned int i = 0; i < root->directories.size(); i++)
        {
            wxString destDir = SubstitutePath(fop.basePath, root->directories[i]->fullPath, fop.destPath);
            fop.lastProgress.QuadPart = 0;
            FileOperationProgress p = FOP_PROGRESS_CONTINUE;
            if (fop.workWatcher)
                p = fop.workWatcher->CurrentFile(root->directories[i]->fullPath);
            if (p == FOP_PROGRESS_CANCEL)
                return p;

            if (MoveFileWithProgress(root->directories[i]->fullPath, destDir, CopyProgressRoutine, &fop, 0) == 0)
            {
                DWORD result = GetLastError();
                if (result == ERROR_NOT_SAME_DEVICE)
                {
                    if (PerfromMove(root->directories[i]) == FOP_PROGRESS_CANCEL)
                        return FOP_PROGRESS_CANCEL;
                }
            }
            else
            {
                fop.currentProgress.QuadPart += root->directories[i]->totalSize.QuadPart;
                ((App*) wxTheApp)->GetFileOperationManager()->AddCurrentWork(root->directories[i]->totalSize);
                FileOperationProgress p = FOP_PROGRESS_CONTINUE;
                if (fop.workWatcher)
                    p = fop.workWatcher->CurrentWork(fop.currentProgress);
                if (p == FOP_PROGRESS_CANCEL)
                    return p;
            }
        }
    }
    else if (fop.type == FOP_DELETE)
    {
        for (unsigned int i = 0; i < root->files.size(); i++)
        {
            FileOperationProgress p = FOP_PROGRESS_CONTINUE;
            if (fop.workWatcher)
                p = fop.workWatcher->CurrentFile(root->files[i]);
            if (p == FOP_PROGRESS_CANCEL)
                return p;

            DWORD dwAttrs  = GetFileAttributes(root->files[i]);
            if (dwAttrs & FILE_ATTRIBUTE_READONLY)
            {
                dwAttrs = dwAttrs & ~FILE_ATTRIBUTE_READONLY;
                SetFileAttributes(root->files[i], dwAttrs);
            }
            
			wxRemoveFile(root->files[i]);
            
            fop.currentProgress.QuadPart += 1;
            if (fop.workWatcher)
                fop.workWatcher->CurrentWork(fop.currentProgress);
            LARGE_INTEGER l;
            l.QuadPart = 1;
            ((App*) wxTheApp)->GetFileOperationManager()->AddCurrentWork(l);
        }
        for (unsigned int i = 0; i < root->directories.size(); i++)
        {
            FileOperationProgress p = PerfromOperation(root->directories[i]);
            if (p == FOP_PROGRESS_CANCEL)
                return p;
        }
        if (root->fullPath != fop.basePath)
        {
            ((App*) wxTheApp)->GetDirectoryCache()->UnwatchDirectory(root->fullPath);
            wxRmdir(root->fullPath);
        }
    }
    return FOP_PROGRESS_CONTINUE;
}
FileOperationProgress FileOperationThread::PerfromMove(FileTreeNodePtr root)
{
    if (root->fullPath != fop.basePath)
    {
        wxString destDir = SubstitutePath(fop.basePath, root->fullPath, fop.destPath);
        if (!wxDirExists(destDir))
            wxMkdir(destDir);
    }
    for (unsigned int i = 0; i < root->files.size(); i++)
    {
        wxString destFile = SubstitutePath(fop.basePath, root->files[i], fop.destPath);
        fop.lastProgress.QuadPart = 0;
        FileOperationProgress p = FOP_PROGRESS_CONTINUE;
        if (fop.workWatcher)
            p = fop.workWatcher->CurrentFile(root->files[i]);
        if (p == FOP_PROGRESS_CANCEL)
            return p;

        if (MoveFileWithProgress(root->files[i], destFile, CopyProgressRoutine, &fop, MOVEFILE_COPY_ALLOWED) == 0  && GetLastError() == ERROR_REQUEST_ABORTED)
            return FOP_PROGRESS_CANCEL;
    }
    for (unsigned int i = 0; i < root->directories.size(); i++)
    {
        FileOperationProgress p = PerfromMove(root->directories[i]);
        if (p == FOP_PROGRESS_CANCEL)
            return p;
    }
    if (root->fullPath != fop.basePath)
    {
        ((App*) wxTheApp)->GetDirectoryCache()->UnwatchDirectory(root->fullPath);
        wxRmdir(root->fullPath);
    }
    return FOP_PROGRESS_CONTINUE;
}
FileOperationThread::FileOperationThread(const FileOperation &pfop) : fop(pfop)
{

}
wxThread::ExitCode FileOperationThread::Entry()
{
    FileTreeBuilder b;
    b.BuildTree(fop.basePath, fop.directories, fop.files, fop.type == FOP_COPY || fop.type == FOP_MOVE);
    fop.totalFileSize = b.GetTotalFileSize();
    fop.numberOfFiles = b.GetNumberOfFiles();
    fop.currentProgress.QuadPart = 0;
    if (fop.type == FOP_COPY || fop.type == FOP_MOVE)
        fop.totalWork = fop.totalFileSize;
    else if (fop.type == FOP_DELETE)
        fop.totalWork.QuadPart = fop.numberOfFiles;

    ((App*) wxTheApp)->GetFileOperationManager()->AddTotalWork(fop.totalWork);
    if (fop.workWatcher)
        fop.workWatcher->TotalWork(fop.totalWork);
    FileOperationProgress p = PerfromOperation(b.GetRoot());

    ((App*) wxTheApp)->GetFileOperationManager()->SubtractCurrentWork(fop.currentProgress);
    ((App*) wxTheApp)->GetFileOperationManager()->SubtractTotalWork(fop.totalWork);

    return 0;
}
FileOperation &FileOperationThread::GetFileOperation()
{
    return fop;
}